fix: port upstream #42 fixes (EIS error contract, at-spi launcher, screenshot D-Bus-first) - #10
Merged
Merged
Conversation
…meError
Unprotected get_object/Interface/connectToEIS calls crashed the whole
session_start/session_connect: dbus.DBusException is not a RuntimeError,
and core.py catches only RuntimeError to degrade to "no input backend".
Wrap the dbus block and re-raise as
RuntimeError("KWin EIS interface unavailable: {exc}") with error chaining
(adopted from upstream isac322#42).
…oded Arch path The bash wrapper hardcoded /usr/lib/at-spi-bus-launcher (Arch layout). On Debian/Ubuntu/Fedora the binary lives in /usr/libexec (or /usr/lib/at-spi2-core), so the wrapper line silently no-oped and the session's accessibility bus was dead. Resolve on the Python side before assembling the wrapper: first existing candidate from (/usr/libexec, /usr/lib, /usr/lib/at-spi2-core), then shutil.which, then the Arch default (adopted from upstream isac322#42).
…_file capture_screenshot_to_file unconditionally invoked the spectacle CLI, contrary to its own documentation, and minimal/virtual sessions may not have spectacle installed at all. Try the ScreenShot2 D-Bus capture first and fall back to spectacle on DBusException/RuntimeError; when both fail, raise a RuntimeError carrying both causes (adopted from upstream isac322#42). The shared single-frame helper _capture_raw_frame drains the pixel pipe concurrently with the D-Bus call (KWin streams pixels before replying), a reader thread owns read_fd and closes it in its finally block, and the call carries a 5s timeout instead of dbus-python's 25s default. The frame burst path now goes through the same helper; empty frames keep being skipped there instead of aborting the burst.
📝 Docs & SEO ReviewSource files changed in this PR: Consistency check results:
|
VibeProgramm
added a commit
that referenced
this pull request
Sep 6, 2026
…end degradation logging, test cleanups (#11) * fix(session): Arch default as last-resort at-spi launcher fallback, quote it in the wrapper The final fallback of _at_spi_bus_launcher returned candidates[0] (/usr/libexec, the Debian/Ubuntu/Fedora layout), which does not exist on Arch — a session where neither a candidate file nor PATH lookup finds the launcher embedded a dead path into the wrapper. The last resort is now the Arch default /usr/lib/at-spi-bus-launcher (literal, not a candidate index, so reordering candidates cannot repoint it). The resolved path is shlex.quote'd when embedded into the bash wrapper; shlex.quote leaves plain paths untouched, so behavior is unchanged on typical distros. Test updated to expect the Arch default; the wrapper test now also covers a path that requires quoting. * fix(core): log the input-backend degradation reason instead of swallowing it Both InputBackend failure sites (session_start, session_connect) caught RuntimeError and silently degraded to 'no input backend' / ydotool, so the actual reason (EIS unavailable, dbus failure, libei load error) was lost. Each site now logs a warning with the exception text before degrading; backend selection itself is unchanged. kwin_mcp has no logging module anywhere else; logging-to-stderr is the standard channel for stdio MCP servers whose stdout carries the protocol. * chore(tests): remove dead assignment, tautological assert, add type hints - test_input_eis_error: dropped a dead _client_with_bus(object()) assignment immediately overwritten by _client_with_bus(_OkBus()); no side effects (pure constructor stub). - test_screenshot_fallback: 'assert Image' was always true; replaced with a real assertion that phase 2 never calls Image.frombytes for the skipped empty frame. - Type hints on fakes/helpers in both files (monkeypatch -> pytest. MonkeyPatch, tmp_path -> Path, fake signatures), per CONTRIBUTING style rules. Pre-existing tests outside the PR #10 diff untouched. * chore(release): v0.8.2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Точечный порт трёх фиксов из upstream-PR isac322#42 (не вмержен в апстрим, апстрим мержит редко — решение форка: порт, не merge): контракт ошибок EIS, резолвер at-spi-bus-launcher, ScreenShot2-first для
capture_screenshot_to_file. Три атомарных коммитаfix(input)/fix(session)/fix(screenshot)+chore(release): v0.8.1(версия, CHANGELOG, манифесты плагинов через sync_plugin_version.py).Motivation
Контракт на результат — issue #8. Детали по каждому фиксу ниже.
1. EIS error contract —
fix(input)EISClient._setup(input.py): dbus-частьget_object → Interface → connectToEISобёрнута вtry/except dbus.DBusException→RuntimeError("KWin EIS interface unavailable: {exc}")с цепочкойfrom exc. Раньше любое D-Bus-падение роняло весьsession_start/session_connect:dbus.DBusException— неRuntimeError, аcore.pyловит толькоRuntimeErrorдля деградации до "no input backend". Больше ничего не тронуто: ToolError-логика_negotiate_devices(форковая, строже апстрима) не затронута.2. Резолвер at-spi-bus-launcher —
fix(session)В bash-обёртке был захардкожен архловский путь
/usr/lib/at-spi-bus-launcher; на Debian/Ubuntu/Fedora бинарник в/usr/libexec/at-spi-bus-launcher→ тихий no-op, мёртвая accessibility-шина. Новый модульный резолвер_at_spi_bus_launcher(): кандидаты/usr/libexec/at-spi-bus-launcher,/usr/lib/at-spi-bus-launcher,/usr/lib/at-spi2-core/at-spi-bus-launcher(первый существующий поPath(...).exists()), затемshutil.which, затем дефолт-кандидат Arch. Резолв происходит на Python-стороне до генерации обёртки. На Arch (единственная платформа форка на практике) путь не меняется. Env-логика_build_env(KDE_FULL_SESSION и пр.) не тронута.3. ScreenShot2-first для screenshot —
fix(screenshot)capture_screenshot_to_fileбезусловно звал spectacle, вопреки своей документации; в минимальных/виртуальных сессиях spectacle может отсутствовать. Теперь: сначала ScreenShot2 D-Bus (адаптация логики_capture_frame_burst_dbusдля одиночного кадра), приdbus.DBusException/RuntimeError— fallback_capture_via_spectacle, при двойном падении —RuntimeErrorс обеими ошибками. Контракт возвращаемого значения (Path, как зовёт core.py) не изменён.Пиксельный pipe теперь дренируется конкурентно (поток-читатель,
os.read-цикл, fd закрыт вfinallyчитателя — KWin стримит пиксели до D-Bus-ответа), dbus-вызов сtimeout=5.0вместо 25-секундного дефолта dbus-python.Отклонение от апстрима (осознанное): empty-check кадра вынесен из хелпера
_capture_raw_frameвcapture_screenshot_dbus— frame-burst исторически скипает пустые кадры (phase 2), и апстримный вариант (RuntimeError на пустой кадр внутри хелпера) ронял бы весь burst. Поведение frame-burst не изменилось.Чего не делал (вне объёма, issue #8)
Остальные фиксы isac322#42: (a) KDE env — у форка осознанно инвертировано; (b) bounded handshake — уже есть сильнее (adopted из isac322#50); (d) GI enum — не срочно; (g)
EI_EVENT_DEVICE_RESUMED— уже есть, строже апстрима. Docker e2e — отдельная отложенная issue #9.How to Test
Локальный прогон (соответствует CI):
uv run ruff check .— pass (baseline на main: тоже pass)uv run ruff format --check .— passuv run ty check— passuv run pytest tests/ -q— 65 passed (baseline: 56 passed; +9 новых тестов). Тесты, требующие живого KWin, в наборе отсутствуют — скипнутых нет ни до, ни после.Живой прогон не выполнялся агентом (нужен живой KDE Wayland-десктоп). Человеку стоит проверить:
session_connectк живому десктопу → при недоступном EIS-интерфейсе сессия стартует со строкой "No input backend..." вместо падения;session_start(виртуальная сессия) → AT-SPI-дерево непустое (путь резолвера на Arch должен остаться/usr/lib/at-spi-bus-launcher);screenshotв виртуальной сессии → кадр снимается через ScreenShot2 (быстро), при недоступности — fallback spectacle.Checklist
uv run ruff check .passesuv run ruff format --check .passesuv run ty checkpassesCloses
Closes #8